package com.demo.config;

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.HostNameUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.demo.model.ClusterGroupEntity;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Optional;
import java.util.Properties;

/**
 * 集群流控客户端
 * @author cyj
 **/
@Configuration
public class SentinelConfig implements InitializingBean {

    private static final String GROUP_ID = "SENTINEL_GROUP";

    @Resource
    private NacosConfigProperties nacosConfigProperties;

    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public void afterPropertiesSet() {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
        properties.put(PropertyKeyConst.NAMESPACE, nacosConfigProperties.getNamespace());
        final String flowDataId = String.format("%s-flow-rules", applicationName);
        final String paramDataId = String.format("%s-param-rules", applicationName);
        final String configDataId = String.format("%s-cluster-client-config", applicationName);
        final String clusterMapDataId = String.format("%s-cluster-map", applicationName);

        // 指定当前身份为 Token Client
        ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);

        // Register client dynamic rule data source.
        initDynamicRuleProperty(properties, flowDataId, paramDataId);
        // Register token client related data source.
        // Token client common config:
        initClientConfigProperty(properties, configDataId);
        // 连接Token Server配置
        // Token client assign config (e.g. target token server) retrieved from assign map:
        initClientServerAssignProperty(properties, clusterMapDataId);
    }

    private void initDynamicRuleProperty(Properties properties, String flowDataId, String paramDataId) {
        ReadableDataSource<String, List<FlowRule>> ruleSource = new NacosDataSource<>(properties, GROUP_ID,
                flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(ruleSource.getProperty());

        ReadableDataSource<String, List<ParamFlowRule>> paramRuleSource = new NacosDataSource<>(properties, GROUP_ID,
                paramDataId, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
        ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
    }

    private void initClientConfigProperty(Properties properties, String configDataId) {
        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(properties, GROUP_ID,
                configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));
        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
    }

    private void initClientServerAssignProperty(Properties properties, String clusterMapDataId) {
        // Cluster map format:
        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}]
        // machineId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)
        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(properties, GROUP_ID,
                clusterMapDataId, source -> {
            ClusterGroupEntity groupList = JSON.parseObject(source, new TypeReference<ClusterGroupEntity>() {});
            return Optional.ofNullable(groupList)
                    .map(this::extractClientAssignment)
                    .orElse(null);
        });
        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
    }

    private ClusterClientAssignConfig extractClientAssignment(ClusterGroupEntity group) {
        if (group.getClientSet().contains(getCurrentMachineId())) {
            String ip = group.getIp();
            Integer port = group.getPort();
            return new ClusterClientAssignConfig(ip, port);
        }

        return null;
    }

    private String getCurrentMachineId() {
        // Note: this may not work well for container-based env.
        return HostNameUtil.getIp() + "@" + TransportConfig.getRuntimePort();
    }
}
